Home Categories Archives Friends GitHub

Hackergame 2023 Writeup

Web

更深更暗

小 E 正在收看电视新闻。

「诶,你知道吗,『泰坦』号潜水艇失事了!」小 E 对旁边的小 C 说。

小 C 凑近电视机,看了一眼新闻里的画面。

「是我眼花了吗?我刚刚有一瞬间好像在残骸上看到了一个 flag?」小 C 惊讶地说。

「玩 CTF 玩的。」小 E 对此不以为然,「一定是你看错了。」

小 C 却十分相信自己没有看错。

直接在页面中搜索 flag 即可。

赛博井字棋

那一年的人机大战,是 AlphaGo 对阵柯洁,最终比分 3-0。当时我看见柯洁颓坐在椅子上泣不成声,这个画面我永生难忘。那一刻我在想,如果我能成为一名棋手,我一定要赢下人工智能。如今 AI 就在眼前,我必须考虑这会不会是我此生仅有的机会。重铸人类围棋荣光,我辈义不容辞!

……

但是围棋实在太难了,你决定先从井字棋开始练习。

根据井字棋游戏的规则,如果对方没有失误,那么只能打成平手。

测试发现,每次落子时会发送携带有 {"x":"...","y":"..."} 的 POST 请求。可以通过 Edge 的网络控制台手动编辑重放请求来“吃掉”对方下的子。

组委会模拟器

每年比赛,组委会的一项重要工作就是时刻盯着群,并且撤回其中有 flag 的消息。今年因为人手紧张,组委会的某名同学将这项工作外包给了你,你需要连续审查 1000 条消息,准确无误地撤回其中所有含 flag 的消息,并且不撤回任何不含 flag 的消息。

本题中,你需要撤回的 "flag" 的格式为 hack[...],其中方括号内均为小写英文字母,点击消息即可撤回。你需要在 3 秒内撤回消息,否则撤回操作将失败。在全部消息显示完成后等待几秒,如果你撤回的消息完全正确(撤回了全部需要撤回的消息,并且未将不需要撤回的消息撤回),就能获得本题真正的 flag

分析后发现题目会请求 /getMessages/deleteMessage/getflag 几个 API。可以手动编写脚本完成删除任务。

const url = 'http://202.38.93.111:10021'
const cookie = 'session='
async function sleep (ms) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

fetch(url + '/api/getMessages', {
  method: 'POST',
  headers: {
    cookie
  }
})
  .then(res => res.json())
  .then(data => {
    console.log('got messages')
    const tasks = data.messages
      .filter(msg => msg.text.match(/hack\[.+/))
      .map(async (msg, i) => {
        await sleep(msg.delay * 1000)
        fetch(url + '/api/deleteMessage', {
          method: 'POST',
          body: JSON.stringify({ id: i }),
          headers: {
            cookie,
            'Content-Type': 'application/json'
          }
        })
          .then(res => res.json())
          .then(data => {
            console.log(i, msg.text)
            if (data.error) throw new Error(data.error)
          })
      })
    console.log('start deleting')
    return Promise.all(tasks)
  })
  .then(() => sleep(2000))
  .then(() => {
    fetch(url + '/api/getflag', {
      method: 'POST',
      headers: {
        cookie
      }
    })
      .then(res => res.json())
      .then(data => console.log(data))
  })

发现官方 WP 的方案更加简单,自己想复杂了。直接在控制台定时删除即可。

setInterval(() =>
    Array.from(document.querySelectorAll(".fakeqq-message__bubble"))
        .filter((element) => element.innerHTML.indexOf("hack[") != -1)
        .forEach((element) => element.click())
    , 100)

HTTP 集邮册

「HTTP 请求一瞬间就得到了响应,但是,HTTP 响应的 status line、header 和 body 都是确实存在的。如果将一个一个 HTTP 状态码收集起来,也许就能变成……变成……变成……」

「flag?」

「就能变成 flag!」


本题中,你可以向一个 nginx 服务器(对应的容器为默认配置下的 nginx:1.25.2-bookworm)发送 HTTP 请求。你需要获取到不同的 HTTP 响应状态码以获取 flag,其中:

关于无状态码的判断逻辑如下:

crlf = buf.find(b"\r\n")
if buf.strip() != b"":
    try:
        if crlf == -1:
            raise ValueError("No CRLF found")
        status_line = buf[:crlf]
        http_version, status_code, reason_phrase = status_line.split(b" ", 2)
        status_code = int(status_code)
    except ValueError:
        buf += "(无状态码)".encode()
        status_code = None

其他的没有再研究了。这里引用一下官方 WP 中各剩下状态码的做法。